#include "base/simapi.h"
#include "base/Configuration.h"
#include "scc/SCCSimulator.h"
#include "scc/MESICache.h"
#include "util/Vector.h"

NEW_DELETE_OPERATORS_CPP(SCCSimulator,1);

extern "C" Simulator* makeSimulator_scc () {
    return NEW SCCSimulator();
}

SCCSimulator::SCCSimulator (const char* sysName) :
    Simulator((sysName != NULL) ? sysName : "scc")
{
    global_statistics->addBucket("Cache-Shared-Stall");
    global_statistics->addBucket("Cache-Shared-Unavailable");
    global_statistics->addCounter("Cache-Shared-Accesses");
    global_statistics->addCounter("Cache-Shared-Hits");
    global_statistics->addCounter("Cache-Shared-Misses");
    global_statistics->addCounter("Cache-Shared-Accesses-By-Log");
    global_statistics->addCounter("Cache-Shared-Hits-By-Log");
    global_statistics->addCounter("Cache-Shared-Misses-By-Log");    
}

SCCSimulator::~SCCSimulator () {
}

void SCCSimulator::buildSystem (void* mainX, 
                                int argc, 
                                const char** argv, 
                                const char** envp) 
{
    buildSystem_bus_cached(mainX, argc, argv, envp, MODE_LOCK);
}


uint SCCSimulator::getBusQueueSize () {
    return global_cfg->getIntegerArgument("bus_queue_size",
                                          "number of buffer slots in bus",
                                          32,
                                          CONFIG_POW2);
}

uint SCCSimulator::getBusWidth () {
    return global_cfg->getIntegerArgument("bus_width",
                                          "width of bus in bytes",
                                          32,
                                          CONFIG_POW2);
}

uint SCCSimulator::getBusArbDelay () {
    return global_cfg->getIntegerArgument("bus_arbdelay",
                                          "bus arbitration delay in cycles",
                                          6,
                                          CONFIG_UNSIGNED);
}

uint SCCSimulator::getCacheSize (uint level, uint default_size, bool shared) {
    char name[256];
    char desc[256];
    if (shared) {
        sprintf(name, "cache_shared_size");
        sprintf(desc, "shared data cache size in bytes");
    } else {
        sprintf(name, "cache_L%d_size", level);
        sprintf(desc, "L%d data cache size in bytes", level);
    }
    if (default_size == 0) {
        if (level == 1) {
            default_size = 64*1024;
        } else if (level == 2 && !shared) {
            default_size = 512*1024;
        } else {
            ASSERT(shared);
            default_size = 8*1024*1024;
        }
    }
    return global_cfg->getIntegerArgument(name, desc, default_size, CONFIG_POW2);
}

uint SCCSimulator::getCacheAssoc (uint level, bool shared) {
    char name[256];
    char desc[256];
    if (shared) {
        sprintf(name, "cache_shared_assoc");
        sprintf(desc, "shared data cache associativity");
    } else {
        sprintf(name, "cache_L%d_assoc", level);
        sprintf(desc, "L%d data cache associativity", level);
    }
    return global_cfg->getIntegerArgument(name,
                                          desc,
                                          4,
                                          CONFIG_POW2);
}

uint SCCSimulator::getCacheVictimCacheSize (uint level, bool shared) {
    char name[256];
    char desc[256];
    if (shared) {
        sprintf(name, "cache_shared_vcsize");
        sprintf(desc, "shared data cache victim cacne size");
    } else {
        sprintf(name, "cache_L%d_vcsize", level);
        sprintf(desc, "L%d data cache victim cacne size", level);
    }
    return global_cfg->getIntegerArgument(name,
                                          desc,
                                          8,
                                          CONFIG_UNSIGNED);
}

uint SCCSimulator::getCacheDelay (uint level, bool shared) {
    char name[256];
    char desc[256];
    if (shared) {
        sprintf(name, "cache_shared_delay");
        sprintf(desc, "penalty to access shared data cache in cycles");
    } else {
        sprintf(name, "cache_L%d_delay", level);
        sprintf(desc, "penalty to access L%d data cache in cycles", level);
    }
    return global_cfg->getIntegerArgument(name,
                                          desc,
                                          (level == 1) ? 0 : 10,
                                          CONFIG_UNSIGNED);
}

uint SCCSimulator::getCacheMSHRs (uint level, bool shared) {
    char name[256];
    char desc[256];
    if (shared) {
        sprintf(name, "cache_shared_mshrs");
        sprintf(desc, "shared data cache MSHRs");
    } else {
        sprintf(name, "cache_L%d_mshrs", level);
        sprintf(desc, "L%d data cache MSHRs", level);
    }
    return global_cfg->getIntegerArgument(name,
                                          desc,
                                          level == 1 ? 1 : 8,
                                          CONFIG_POW2);
}

bool SCCSimulator::hasSharedCache () {
    return global_cfg->getBooleanArgument("cache_shared",
                                          "use a shared cache below the main bus",
                                          true);
}

bool SCCSimulator::isSharedCacheBanked () {
    return global_cfg->getBooleanArgument("cache_shared_bank",
                                          "split the shared L2 into the banks of num_cpus",
                                          true);
}

void SCCSimulator::buildSystem_bus_cached (void* mainX,
                                           int argc,
                                           const char** argv,
                                           const char** envp,
                                           uint _mode)
{
    mode = _mode;
    buildProcessors(mainX, argc, argv, envp);

    memory = NEW Memory("MM", getMemLatency());

    // add the main memory
    entities.push_back(memory);
    activate(memory);

    // add the bus
    systemBus = buildBus();
    entities.push_back(systemBus);
    activate(systemBus);

    bool shared_cache = hasSharedCache();
    int snoopDelay= global_cfg->getIntegerArgument("cache_snoop_delay",
                                                   "cache snoop delay",
                                                   2);

    //
    // create caches.
    //

    MESICache* l1[cfg.num_cpus];
    MESICache* shared = NULL;
    for (uint i = 0; i < cfg.num_cpus; i++) {
        char name[9];
        Processor* P = procs[i];
        strcpy(name, P->getName());
        strcat(name, "L1");

        l1[i] = buildPrivateCache(name,
                                  1,
                                  P->getStatistics(),
                                  snoopDelay);
        entities.push_back(l1[i]);
        activate(l1[i]);
    }

    if (shared_cache) {
        // shared cache doesn't belong to a processor.
        shared = buildSharedCache("SHARED",
                                  2,
                                  getStatistics(),
                                  1);

        entities.push_back(shared);
        activate(shared);
    }

//
// build the routing table
//
// abbreviation
// X = no route
// r = read
// w = write
// res = response to refill request or eviction request
// fsh = cache flush or cache line flush
// fsh-res = flush response
// ref = refill
// snp = snoop
// snp-res = snoop response including write-back
// snp-res-res = response of memory for snoop response from cache
// ev = evict
// evi = eviction to force data inclusion
//

    if (!shared_cache) {
//
// route table for private L1 only
//
// entity\dest  proc(P)         L1       bus(B)         mem(M)
// proc(P)        X             X          X          L1(r/w/fsh)
// L1         P(res/fsh-res)    X       B(snp-res)    B(ref/ev/fsh)
// bus(B)      L1(fsh-res)  L1(res/snp)    X          M(ref/ev/fsh/snp-res)
// mem(M)      B(fsh-res)     B(res)    B(snp-res-res)     X
//
        for (uint i = 0; i < cfg.num_cpus; i++) {
            // processor's routing table
            Processor* P = procs[i];
            P->addRoute(memory, l1[i]);
            P->addRoute(P, P);

            // L1's routing tables
            l1[i]->addRoute(getTop(), P);
            l1[i]->addRoute(P, P);
            l1[i]->addRoute(memory, systemBus);
            l1[i]->addRoute(systemBus, systemBus);

            // bus's routing tables
            systemBus->addRoute(P, l1[i]);
            systemBus->addRoute(l1[i], l1[i]);

            // memory's routing tables
            memory->addRoute(P, systemBus);
            memory->addRoute(l1[i], systemBus);
            // memory->addRoute(systemBus, systemBus);

            // snoop message
            systemBus->registerRequestSnoopDev(l1[i]);
        }

        systemBus->addRoute(memory, memory);
        memory->addRoute(systemBus, systemBus);
        
        //systemBus->registerResponseSnoopDev(memory);
    } else {
//
// route table for private L1 and shared L2
//
// entity\dest  proc(P)         L1        bus(B)      L2         mem(M)
// proc(P)        X             X          X          X         L1(r/w/fsh)
// L1        P(res/fsh-res)     X       B(snp-res)    X         B(ref/ev/fsh)
// bus(B)     L1(fsh-res)   L1(res/snp)    X       L2(snp-res)  L2(ref/ev/fsh)
// L2         B(fsh-res)      B(res)  B(snp-res-res)  X         M(ref/ev/fsh)
// mem(M)     L2(fsh-res)       X          X        L2(res)         X
//
        for (uint i = 0; i < cfg.num_cpus; i++) {
            // processor's routing table
            Processor* P = procs[i];
            P->addRoute(memory, l1[i]);
            P->addRoute(P, P);

            // L1's routing tables
            l1[i]->addRoute(getTop(), P);
            l1[i]->addRoute(P, P);
            l1[i]->addRoute(systemBus, systemBus);
            l1[i]->addRoute(memory, systemBus);

            // bus's routing tables
            systemBus->addRoute(P, l1[i]);
            systemBus->addRoute(l1[i], l1[i]);

            // shared cache's routing tables
            shared->addRoute(P, systemBus);
            shared->addRoute(l1[i], systemBus);
            //l2[0]->addRoute(systemBus, systemBus);
            //l2[0]->addRoute(memory, memory);

            // memory's routing tables
            memory->addRoute(P, shared);
            //memory->addRoute(l2[0], l2[0]);

            // snoop message
            systemBus->registerRequestSnoopDev(l1[i]);
            // systemBus->registerResponseSnoopDev(l2[0]);            
        }

        systemBus->addRoute(memory, shared);
        shared->addRoute(systemBus, systemBus);
        shared->addRoute(memory, memory);
        memory->addRoute(shared, shared);
        
        //systemBus->registerResponseSnoopDev(l2[0]);
    }
}


//
// setCacheStats is only for private caches...
//
void SCCSimulator::setCacheStats (uint level,
                                  StatisticsCollection* stats)
{
    char unavail_name[256];
    char acc_name[256];

    buckets[0]  = stats->getBucket("Cycles-Memory-Stall");
    sprintf(unavail_name, "Cycles-L%d-Unavailable", level);
    buckets[1]  = stats->getBucket(unavail_name);

    sprintf(acc_name, "L%d-Accesses", level);
    counters[0] = stats->getCounter(acc_name);
    sprintf(acc_name, "L%d-Hits", level);
    counters[1] = stats->getCounter(acc_name);
    sprintf(acc_name, "L%d-Misses", level);
    counters[2] = stats->getCounter(acc_name);
    sprintf(acc_name, "L%d-Accesses-By-Log", level);
    counters[3] = stats->getCounter(acc_name);
    sprintf(acc_name, "L%d-Hits-By-Log", level);
    counters[4] = stats->getCounter(acc_name);
    sprintf(acc_name, "L%d-Misses-By-Log", level);
    counters[5] = stats->getCounter(acc_name);
}



SplitBus* SCCSimulator::buildBus () {
    return buildNormalBus<SplitBus>();
}

MESICache* SCCSimulator::buildPrivateCache (const char* name,
                                            uint level,
                                            StatisticsCollection* stats,
                                            uint snoopDelay)
{
    setCacheStats(level, stats);
    return NEW MESICache(name,
                         getCacheSize(level),
                         cfg.blocksize,
                         getCacheAssoc(level),
                         getCacheVictimCacheSize(level),
                         getCacheDelay(level),
                         getCacheMSHRs(level),
                         snoopDelay,
                         NEW MESIFlags(0, cfg.blocksize),
                         counters[0],
                         counters[1],
                         counters[2],
                         buckets[0],
                         buckets[1]);
}

MESICache* SCCSimulator::buildSharedCache (const char* name, 
                                           uint level, 
                                           StatisticsCollection* stats, 
                                           uint snoopDelay,
                                           bool banked)
{
    // we use MESI cache for shared b/c its simpler
    //
    buckets[0]  = global_statistics->getBucket("Cache-Shared-Stall");
    buckets[1]  = global_statistics->getBucket("Cache-Shared-Unavailable");

    counters[0] = global_statistics->getCounter("Cache-Shared-Accesses");
    counters[1] = global_statistics->getCounter("Cache-Shared-Hits");
    counters[2] = global_statistics->getCounter("Cache-Shared-Misses");
    counters[3] = global_statistics->getCounter("Cache-Shared-Accesses-By-Log");
    counters[4] = global_statistics->getCounter("Cache-Shared-Hits-By-Log");
    counters[5] = global_statistics->getCounter("Cache-Shared-Misses-By-Log");

    return NEW MESICache(name,
                         (banked)? getCacheSize(level, 0, true)/cfg.num_cpus : getCacheSize(level, 0, true),
                         cfg.blocksize,
                         getCacheAssoc(level, true),
                         getCacheVictimCacheSize(level, true),
                         getCacheDelay(level, true),
                         getCacheMSHRs(level, true),
                         snoopDelay,
                         NEW MESIFlags(0, cfg.blocksize),
                         counters[0],
                         counters[1],
                         counters[2],
                         buckets[0],
                         buckets[1]);
}

void SCCSimulator::hookResetQuiesce () {
    SplitBus::resetQuiesceReqCount();
}

void SCCSimulator::hookPrintStatistics (FILE* statFile) {
    Simulator::hookPrintStatistics(statFile);
    if (getBus() != NULL) {
        getBus()->printStatistics(statFile);
    }
    fputs("\n", statFile);

    for (uint i=0; i<cfg.num_cpus; i++) {
        procs[i]->printStatisticsCollection(statFile);
        fputs("\n", statFile);
    }
}

void SCCSimulator::hookStatsCountVotes () {
    Simulator::hookStatsCountVotes();

    if (getBus()) {
        getBus()->countVotes();
    }
}
